#include "sysconfig.h"
#include "debug_win32.h"
#include "debug.h"
#include "uae.h"
#include "win32gui.h"
#include "custom.h"
#include "events.h"

int Logger::ConsoleOpen = 0;
int Logger::realconsole = 0;
HANDLE Logger::stdinput = 0;
HANDLE Logger::stdoutput = 0;
int Logger::bootlogmode = 0;
CRITICAL_SECTION Logger::cs;
bool Logger::cs_init = false;
FILE* Logger::debugfile = nullptr;
int Logger::debugger_type = -1;
// int Logger::lfdetected = 1;
TCHAR* Logger::console_buffer = nullptr;
int Logger::console_buffer_size = 0;

#define WRITE_LOG_BUF_SIZE 4096

void Logger::getconsole()
{
    CONSOLE_SCREEN_BUFFER_INFO csbi;

    stdinput = GetStdHandle(STD_INPUT_HANDLE);
    stdoutput = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleMode(stdinput, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_OUTPUT);
    SetConsoleCP(65001);
    SetConsoleOutputCP(65001);
    if (GetConsoleScreenBufferInfo(stdoutput, &csbi))
    {
        if (csbi.dwMaximumWindowSize.Y < 900)
        {
            csbi.dwMaximumWindowSize.Y = 900;
            SetConsoleScreenBufferSize(stdoutput, csbi.dwMaximumWindowSize);
        }
    }
}

void Logger::open_console_window()
{
    AllocConsole();
    getconsole();
    ConsoleOpen = -1;
    ConsoleReopen();
}

void Logger::openconsole()
{
    if (realconsole)
    {
        if (debugger_type == 2)
        {
            open_debug_window();
            ConsoleOpen = 1;
        }
        else
        {
            close_debug_window();
            ConsoleOpen = -1;
        }
        return;
    }
    if (debugger_active && (debugger_type < 0 || debugger_type == 2))
    {
        if (ConsoleOpen > 0)
            return;
        if (debugger_type < 0)
        {
            regqueryint(nullptr, L"DebuggerType", &debugger_type);
            if (debugger_type <= 0)
                debugger_type = 2;
            openconsole();
            return;
        }
        ConsoleClose();
        if (open_debug_window())
        {
            ConsoleOpen = 1;
            return;
        }
        open_console_window();
    }
    else
    {
        if (ConsoleOpen < 0)
            return;
        ConsoleClose();
        open_console_window();
    }
}

void Logger::DebuggerChange(int mode)
{
    if (mode < 0)
        debugger_type = debugger_type == 2 ? 1 : 2;
    else
        debugger_type = mode;
    if (debugger_type != 1 && debugger_type != 2)
        debugger_type = 2;
    regsetint(nullptr, L"DebuggerType", debugger_type);
    openconsole();
}

void Logger::ConsoleReopen()
{
    HWND hwnd;

    if (realconsole)
        return;
    if (ConsoleOpen >= 0)
        return;
    hwnd = GetConsoleWindow();
    if (hwnd)
    {
        int newpos = 1;
        int x, y, w, h;
        if (!regqueryint(nullptr, L"LoggerPosX", &x))
            newpos = 0;
        if (!regqueryint(nullptr, L"LoggerPosY", &y))
            newpos = 0;
        if (!regqueryint(nullptr, L"LoggerPosW", &w))
            newpos = 0;
        if (!regqueryint(nullptr, L"LoggerPosH", &h))
            newpos = 0;
        if (newpos)
        {
            RECT rc;
            rc.left = x;
            rc.top = y;
            rc.right = x + w;
            rc.bottom = y + h;
            if (MonitorFromRect(&rc, MONITOR_DEFAULTTONULL) != nullptr)
            {
                SetForegroundWindow(hwnd);
                SetWindowPos(hwnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE);
            }
        }
    }
}

TCHAR* Logger::setconsolemode(TCHAR* buffer, int maxlen)
{
    TCHAR* ret = nullptr;
    if (buffer)
    {
        console_buffer = buffer;
        console_buffer_size = maxlen;
    }
    else
    {
        ret = console_buffer;
        console_buffer = nullptr;
    }
    return ret;
}

void Logger::console_put(const TCHAR* buffer)
{
    if (console_buffer)
    {
        if (_tcslen(console_buffer) + _tcslen(buffer) < console_buffer_size)
            _tcscat(console_buffer, buffer);
    }
    else
    {
        openconsole();
        writeconsole(buffer);
    }
}

void Logger::ConsoleClose()
{
    if (realconsole)
        return;
    if (ConsoleOpen > 0)
    {
        close_debug_window();
    }
    else if (ConsoleOpen < 0)
    {
        HWND hwnd = GetConsoleWindow();
        if (hwnd && !IsIconic(hwnd))
        {
            RECT r;
            if (GetWindowRect(hwnd, &r))
            {
                r.bottom -= r.top;
                r.right -= r.left;
                regsetint(nullptr, L"LoggerPosX", r.left);
                regsetint(nullptr, L"LoggerPosY", r.top);
                regsetint(nullptr, L"LoggerPosW", r.right);
                regsetint(nullptr, L"LoggerPosH", r.bottom);
            }
        }
        FreeConsole();
    }
    ConsoleOpen = 0;
}

void Logger::writeconsole_2(const TCHAR* buffer)
{
    DWORD temp;

    if (!ConsoleOpen)
        openconsole();

    if (ConsoleOpen > 0)
    {
        WriteOutput(buffer, _tcslen(buffer));
    }
    else if (realconsole)
    {
        fputws(buffer, stdout);
        fflush(stdout);
    }
    else if (ConsoleOpen < 0)
    {
        WriteConsole(stdoutput, buffer, _tcslen(buffer), &temp, 0);
    }
}

void Logger::writeconsole(const TCHAR* buffer)
{
    if (_tcslen(buffer) > 256)
    {
        TCHAR* p = _tcsdup(buffer);
        TCHAR* p2 = p;
        while (_tcslen(p) > 256)
        {
            TCHAR tmp = p[256];
            p[256] = 0;
            writeconsole_2(p);
            p[256] = tmp;
            p += 256;
        }
        writeconsole_2(p);
        free(p2);
    }
    else
    {
        writeconsole_2(buffer);
    }
}

void Logger::ConsoleOut(const TCHAR* format, ...)
{
    va_list parms;
    TCHAR buffer[WRITE_LOG_BUF_SIZE];

    va_start(parms, format);
    _vsntprintf(buffer, WRITE_LOG_BUF_SIZE - 1, format, parms);
    va_end(parms);
    console_put(buffer);
}

TCHAR Logger::ConsoleGetChar()
{
    if (realconsole)
    {
        return getwc(stdin);
    }
    else if (ConsoleOpen < 0)
    {
        DWORD len;
        TCHAR out[2];

        for (;;)
        {
            out[0] = 0;
            ReadConsole(stdinput, out, 1, &len, 0);
            if (len > 0)
                return out[0];
        }
    }
    return 0;
}

int Logger::ConsoleGet(TCHAR* out, int maxlen)
{
    *out = 0;

    if (ConsoleOpen > 0)
    {
        return console_get_gui(out, maxlen);
    }
    else if (realconsole)
    {
        DWORD totallen;

        *out = 0;
        totallen = 0;
        while (maxlen > 0)
        {
            *out = getwc(stdin);
            if (*out == 13)
                break;
            out++;
            maxlen--;
            totallen++;
        }
        *out = 0;
        return totallen;
    }
    else if (ConsoleOpen < 0)
    {
        DWORD len, totallen;

        *out = 0;
        totallen = 0;
        while (maxlen > 0)
        {
            ReadConsole(stdinput, out, 1, &len, 0);
            if (*out == 13)
                break;
            out++;
            maxlen--;
            totallen++;
        }
        *out = 0;
        return totallen;
    }
    return 0;
}

void Logger::ConsoleFlush()
{
    if (ConsoleOpen > 0)
        fflush(stdout);
    else if (realconsole)
        fflush(stdout);
    else if (ConsoleOpen < 0)
        FlushFileBuffers(stdoutput);
}

TCHAR* Logger::writets()
{
    struct tm* t;
    struct _timeb tb;
    static TCHAR out[100];
    TCHAR* p;
    static TCHAR lastts[100];
    TCHAR curts[100];

    if (bootlogmode)
        return nullptr;
    _ftime(&tb);
    t = localtime(&tb.time);
    _tcsftime(curts, sizeof curts / sizeof(TCHAR), L"%Y-%m-%d %H:%M:%S\n", t);
    p = out;
    *p = 0;
    if (_tcsncmp(curts, lastts, _tcslen(curts) - 3))     // "xx\n"
    {
        _tcscat(p, curts);
        p += _tcslen(p);
        _tcscpy(lastts, curts);
    }
    _tcsftime(p, sizeof out / sizeof(TCHAR) - (p - out), L"%S-", t);
    p += _tcslen(p);
    _stprintf(p, L"%03d", tb.millitm);
    p += _tcslen(p);
    if (vsync_counter != 0xffffffff)
        _stprintf(p, L" [%d %03dx%03d]", vsync_counter, current_hpos(), vpos);
    _tcscat(p, L": ");
    return out;
}

void Logger::WriteLine(const TCHAR* format, ...)
{
    va_list parms;
    va_start(parms, format);
    CString str;
    str.FormatV(format, parms);
    va_end(parms);
    Write(str);
    Write(L"\n");
}

void Logger::Write(const TCHAR* format, ...)
{
    int count;
    TCHAR buffer[WRITE_LOG_BUF_SIZE] /*, * ts*/;
    int bufsize = WRITE_LOG_BUF_SIZE;
    TCHAR* bufp;
    va_list parms;

    if (!cs_init)
        return;

    EnterCriticalSection(&cs);
    va_start(parms, format);
    bufp = buffer;
    for (;;)
    {
        count = _vsntprintf(bufp, bufsize - 1, format, parms);
        if (count < 0)
        {
            bufsize *= 10;
            if (bufp != buffer)
                free(bufp);
            bufp = xmalloc(TCHAR, bufsize);
            continue;
        }
        break;
    }
    bufp[bufsize - 1] = 0;
    // if (!_tcsncmp(bufp, L"write ", 6))
    //     bufsize--;
    /*ts =*/ writets();
    // if (bufp[0] == '*')
    //     count++;
    if (Logging::console_logging)
        writeconsole(bufp);
    if (debugfile)
        _ftprintf(debugfile, L"%s", bufp);
    TRACE(bufp);
    // lfdetected = 0;
    // if (_tcslen(bufp) > 0 && bufp[_tcslen(bufp) - 1] == '\n')
    //     lfdetected = 1;
    va_end(parms);
    if (bufp != buffer)
        free(bufp);
    LeaveCriticalSection(&cs);
}

void Logger::Flush()
{
    if (debugfile)
        fflush(debugfile);
    ConsoleFlush();
}

void Logger::FileOut(void* f, const TCHAR* format, ...)
{
    // int count;
    TCHAR buffer[WRITE_LOG_BUF_SIZE];
    va_list parms;
    va_start(parms, format);

    if (f == nullptr)
        return;
    /*count =*/ _vsntprintf(buffer, WRITE_LOG_BUF_SIZE - 1, format, parms);
    openconsole();
    writeconsole(buffer);
    va_end(parms);
}

TCHAR* Logger::BufferOut(TCHAR* buffer, int* bufsize, const TCHAR* format, ...)
{
    va_list parms;
    va_start(parms, format);

    if (buffer == nullptr)
        return nullptr;
    _vsntprintf(buffer, (*bufsize) - 1, format, parms);
    va_end(parms);
    *bufsize -= _tcslen(buffer);
    return buffer + _tcslen(buffer);
}

void Logger::Open(const TCHAR* name, int append, int bootlog)
{
    if (debugfile != 0)
        return;

    if (!cs_init)
        InitializeCriticalSection(&cs);
    cs_init = true;

    if (name != nullptr)
    {
        debugfile = _tfopen(name, append ? L"a, ccs=UTF-8" : L"wt, ccs=UTF-8");
        bootlogmode = bootlog;
    }
    else /*if (1)*/
    {
        TCHAR* c = GetCommandLine();
        if (_tcsstr(c, L" -console"))
        {
            if (GetStdHandle(STD_INPUT_HANDLE) && GetStdHandle(STD_OUTPUT_HANDLE))
            {
                ConsoleOpen = -1;
                realconsole = 1;
                getconsole();
                _setmode(_fileno(stdout), _O_U16TEXT);
                _setmode(_fileno(stdin), _O_U16TEXT);
            }
        }
    }
}

void Logger::Close()
{
    if (debugfile != 0)
    {
        fclose(debugfile);
        debugfile = 0;
    }
}

void Logger::JitAbort(const TCHAR* format, ...)
{
    static int happened;
    // int count;
    TCHAR buffer[WRITE_LOG_BUF_SIZE];
    va_list parms;

    va_start(parms, format);

    /*count =*/ _vsntprintf(buffer, WRITE_LOG_BUF_SIZE - 1, format, parms);
    writeconsole(buffer);
    va_end(parms);
    if (!happened)
        gui_message(L"JIT: Serious error:\n%s", buffer);
    happened = 1;
    uae_reset(1);
}